﻿using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Shop.Core.Redis
{
    public class RedisProvider : IRedisProvider
    {
        private readonly RedisConfig _options;
        private readonly IDatabase _redisDb;

        private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;

        public RedisProvider(IOptions<RedisConfig> options)
        {
            _options = options.Value;
            _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(CreateConnectionMultiplexer);
        }

        private ConnectionMultiplexer CreateConnectionMultiplexer()
        {
            return ConnectionMultiplexer.Connect(_options.ConnectionString);
        }

        public IDatabase GetDatabase()
        {
            return _connectionMultiplexer.Value.GetDatabase();
        }

        public async Task<string> StringGetAsync(string cacheKey)
        {
            var val = await this.GetDatabase().StringGetAsync(cacheKey);
            return val;
        }

        public async Task<bool> StringSetAsync(string cacheKey, string cacheValue, System.TimeSpan? expiration, string when)
        {
            When w = When.Always;

            if (when.Equals("nx", StringComparison.OrdinalIgnoreCase))
            {
                w = When.NotExists;
            }
            else if (when.Equals("xx", StringComparison.OrdinalIgnoreCase))
            {
                w = When.Exists;
            }

            bool flag = await this.GetDatabase().StringSetAsync(cacheKey, cacheValue, expiration, w);
            return flag;
        }

        public async Task<bool> KeyDelAsync(string cacheKey)
        {
            var flag = await this.GetDatabase().KeyDeleteAsync(cacheKey);
            return flag;
        }

        public async Task<(bool Success, string LockValue)> LockAsync(string cacheKey, int timeoutSeconds = 5)
        {
            var lockKey = $"shop:locker:{cacheKey}";
            var lockValue = Guid.NewGuid().ToString();
            var timeoutMilliseconds = timeoutSeconds * 1000;
            var expiration = TimeSpan.FromMilliseconds(timeoutMilliseconds);
            bool flag = await this.GetDatabase().StringSetAsync(lockKey, lockValue, expiration, When.NotExists, CommandFlags.None);
            return (flag, flag ? lockValue : null);
        }

        public async Task<bool> SafedUnLockAsync(string cacheKey, string lockValue)
        {
            var lockKey = $"shop:locker:{cacheKey}";

            var script = @"local invalue = @value
                                    local currvalue = redis.call('get',@key)
                                    if(invalue==currvalue) then redis.call('del',@key)
                                        return 1
                                    else
                                        return 0
                                    end";
            var parameters = new { key = lockKey, value = lockValue };
            var prepared = LuaScript.Prepare(script);
            var result = (int)await this.GetDatabase().ScriptEvaluateAsync(prepared, parameters);
            return result == 1;
        }
    }
}
